package io.noties.markwon.base.core.spans


import android.text.style.LeadingMarginSpan
import io.noties.markwon.base.utils.LeadingMarginUtils

import android.graphics.Canvas
import android.graphics.Paint
import android.os.Build
import android.text.Layout
import androidx.annotation.IntRange
import io.noties.markwon.theme.MarkwonTheme

class BulletListItemSpan(
    private val theme: MarkwonTheme,
    @param:IntRange(from = 0) private val level: Int
) : LeadingMarginSpan {
    private val paint = ObjectsPool.paint()
    private val circle = ObjectsPool.rectF()
    private val rectangle = ObjectsPool.rect()
    override fun getLeadingMargin(first: Boolean): Int {
        return theme.typography.blockMargin
    }

    override fun drawLeadingMargin(
        c: Canvas,
        p: Paint,
        x: Int,
        dir: Int,
        top: Int,
        baseline: Int,
        bottom: Int,
        text: CharSequence,
        start: Int,
        end: Int,
        first: Boolean,
        layout: Layout
    ) {

        // if there was a line break, we don't need to draw anything
        if (!first || !LeadingMarginUtils.selfStart(start, text, this)) {
            return
        }
        paint.set(p)
        applyListItemStyle(paint)
        val save = c.save()
        try {
            val width = theme.typography.blockMargin

            // @since 1.0.6 we no longer rely on (bottom-top) calculation in order to detect line height
            // it lead to bad rendering as first & last lines received different results even
            // if text size is the same (first line received greater amount and bottom line -> less)
            val textLineHeight = (paint.descent() - paint.ascent() + .5f).toInt()
            val side = getBulletWidth(textLineHeight)
            val marginLeft = (width - side) / 2

            // in order to support RTL
            val l: Int
            val r: Int
            run {
                // @since 4.2.1 to correctly position bullet
                // when nested inside other LeadingMarginSpans (sorry, Nougat)
                if (IS_NOUGAT) {

                    // @since 2.0.2
                    // There is a bug in Android Nougat, when this span receives an `x` that
                    // doesn't correspond to what it should be (text is placed correctly though).
                    // Let's make this a general rule -> manually calculate difference between expected/actual
                    // and add this difference to resulting left/right values. If everything goes well
                    // we do not encounter a bug -> this `diff` value will be 0
                    val diff: Int = if (dir < 0) {
                        // rtl
                        x - (layout.width - width * level)
                    } else {
                        width * level - x
                    }
                    val left = x + dir * marginLeft
                    val right = left + dir * side
                    l = Math.min(left, right) + dir * diff
                    r = Math.max(left, right) + dir * diff
                } else {
                    l = if (dir > 0) {
                        x + marginLeft
                    } else {
                        x - width + marginLeft
                    }
                    r = l + side
                }
            }
            val t = baseline + ((paint.descent() + paint.ascent()) / 2f + .5f).toInt() - side / 2
            val b = t + side
            if (level == 0 || level == 1) {
                circle[l.toFloat(), t.toFloat(), r.toFloat()] = b.toFloat()
                val style = if (level == 0) Paint.Style.FILL else Paint.Style.STROKE
                paint.style = style
                c.drawOval(circle, paint)
            } else {
                rectangle[l, t, r] = b
                paint.style = Paint.Style.FILL
                c.drawRect(rectangle, paint)
            }
        } finally {
            c.restoreToCount(save)
        }
    }

    private fun getBulletWidth(height: Int): Int {
        val min = Math.min(theme.typography.blockMargin, height) / 2
        val width: Int
        val bulletWidth = theme.typography.bulletWidth
        width = if (bulletWidth == 0
            || bulletWidth > min
        ) {
            min
        } else {
            bulletWidth
        }
        return width
    }

    private fun applyListItemStyle(paint: Paint) {
        paint.color = if (theme.colorTheme.listItemColor != 0L) {
            theme.colorTheme.listItemColor.toInt()
        } else {
            paint.color
        }
        if (theme.typography.bulletListItemStrokeWidth != 0) {
            paint.strokeWidth = theme.typography.bulletListItemStrokeWidth.toFloat()
        }
    }

    companion object {
        private var IS_NOUGAT =  Build.VERSION_CODES.N == Build.VERSION.SDK_INT || Build.VERSION_CODES.N_MR1 == Build.VERSION.SDK_INT
    }
}